﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using SIG.Data.Repositories.Identity;
using SIG.Infrastructure.Logging;
using SIG.Model.Identity;
using SIG.Infrastructure.Configs;
using SIG.Infrastructure.Cache;
using SIG.Model.ViewModel.Identity;
using SIG.Infrastructure.Helper;
using System.Threading;

using System.Web.Security;
using System.Web;
using Newtonsoft.Json;
using SIG.ViewModel.Front.Identity;
using SIG.Resources.Admin;
using System.Linq.Expressions;
using SIG.Basic.Extensions;

namespace SIG.Service.Identity
{
    public class UserService : EntityService<User>, IUserService
    {
        #region Fields
        private IUserRepository _userRepository;   
        private ILoggingService _logger;
        private ICacheService _cacheService;
       
        #endregion

        public UserService(IUserRepository userRepository, ILoggingService logger, ICacheService cacheService)
            : base(userRepository, cacheService, logger)
        {
            if (userRepository == (IUserRepository)null)
                throw new ArgumentNullException("userRepository");
            if (logger == (ILoggingService)null)
                throw new ArgumentNullException("logger");
            if (cacheService == (ICacheService)null)
                throw new ArgumentNullException("cacheService");
            this._userRepository = userRepository;
            this._logger = logger;
            this._cacheService = cacheService;
           
        }

        public override User GetById(object id)
        {
            var user = _userRepository.GetFirstOrDefault((u => u.Id == (Guid)id),(u => u.Roles));
            return user;
           
        }

      
        /// <summary>
        /// 修改密码
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="oldPassword"></param>
        /// <param name="newPassword"></param>
        /// <returns></returns>
        public int ChangePassword(Guid userId, string oldPassword, string newPassword) {
            User user = GetById(userId);

            if (user != null)
            {
                // byte[] salt = Encoding.UTF8.GetBytes(user.SecurityStamp);
                byte[] salt = Convert.FromBase64String(user.SecurityStamp);
                string pwdHash = Hash.HashPasswordWithSalt(oldPassword, salt);

                if (user.PasswordHash == pwdHash)
                {
                    var securityStamp = Hash.GenerateSalt();
                    pwdHash = Hash.HashPasswordWithSalt(newPassword, securityStamp);

                    user.SecurityStamp = Convert.ToBase64String(securityStamp);
                    user.PasswordHash = pwdHash;
                    this.Update(user);

                    return 0;
                }
                else
                {
                    return 2; //2 原密码不正确
                }

            }

            return 1; //1 用户不存在

        }

        /// <summary>
        /// 重设密码
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="oldPassword"></param>
        /// <param name="newPassword"></param>
        /// <returns></returns>
        public async Task<bool> ResetPassword(string userName, string password)
        {
            User user = await GetUserByName(userName);

            if (user == null)
            {
                return false;
            }

            var securityStamp = Hash.GenerateSalt();
            var pwdHash = Hash.HashPasswordWithSalt(password, securityStamp);

            user.SecurityStamp = Convert.ToBase64String(securityStamp);
            user.PasswordHash = pwdHash;
            this.Update(user);

            return true;           

        }


        public int SearchUsersCount(string username, DateTime? startDate, DateTime? endDate)
        {
            var key = string.Format("{0}s_Search_{1}_{2}_{3}", EntityNames.User, username, startDate, endDate);
            int userSum;

            if (SettingsManager.User.EnableCaching)
            {
                if (_cacheService.IsSet(key))
                {
                    userSum = (int)_cacheService.Get(key);
                }
                else
                {

                    userSum = _userRepository.SearchUsersCount(username, startDate, endDate);
                    _cacheService.Set(key, userSum, SettingsManager.User.CacheDuration);

                }
            }
            else
            {
                userSum = _userRepository.SearchUsersCount(username, startDate, endDate);
            }


            return userSum;
        }


        public IList<User> SearchUsers(int pageIndex, int pageSize, string username, DateTime? startDate, DateTime? endDate)
        {
            var key = string.Format("{0}s_Search_{1}_{2}_{3}_{4}_{5}", EntityNames.User, pageIndex, pageSize, username, startDate, endDate);

            List<User> diariesDTO;
            if (SettingsManager.User.EnableCaching)
            {
                if (_cacheService.IsSet(key))
                {
                    diariesDTO = (List<User>)_cacheService.Get(key);
                }
                else
                {
                    diariesDTO = GetUsers(pageIndex, pageSize, username, startDate, endDate);
                    _cacheService.Set(key, diariesDTO, SettingsManager.User.CacheDuration);
                }
            }
            else
            {
                diariesDTO = GetUsers(pageIndex, pageSize, username, startDate, endDate);
            }

            return diariesDTO;
        }

        //获取源，做DTO处理
        private List<User> GetUsers(int pageIndex, int pageSize, string username, DateTime? startDate, DateTime? endDate)

        {
            // List<User> diariesDTO;
            var diaries = _userRepository.SearchUsers(pageIndex, pageSize, username, startDate, endDate).ToList();

            //diariesDTO = Mapper.Map<IEnumerable<User>, IEnumerable<UserDTO>>(diaries.AsEnumerable()).ToList();
            return diaries;
        }



        public List<User> GetPagedElements(int pageIndex, int pageSize, string keyword, DateTime? startDate, DateTime? endDate,
            int? roleId, out int totalCount)
        {

            //get list count
            var keyCount = $"{EntityNames.User}_ListCount_{keyword}_{startDate}_{endDate}_{roleId}";

            var totalIQuery = _userRepository.GetAllElements();
            if (!string.IsNullOrEmpty(keyword))
                totalIQuery = totalIQuery.Where(g => g.UserName.Contains(keyword));
            if (startDate != null)
                totalIQuery = totalIQuery.Where(m => m.CreateDate >= startDate);
            if (endDate != null)
                totalIQuery = totalIQuery.Where(m => m.CreateDate <= endDate);            
            if (roleId > 0) 
                totalIQuery = totalIQuery.Where(g => g.Roles.Any(m => m.Id == (int)roleId));
            
               

            if (SettingsManager.User.EnableCaching)
            {
                if (_cacheService.IsSet(keyCount))
                {
                    totalCount = (int)_cacheService.Get(keyCount);
                }
                else
                {
                    totalCount = totalIQuery.Count();
                    _cacheService.Set(keyCount, totalCount, SettingsManager.User.CacheDuration);
                }
            }
            else
            {
                totalCount = totalIQuery.Count();
            }


            //get list
            var key = $"{EntityNames.User}_List_{pageIndex}_{pageSize}_{keyword}_{startDate}_{endDate}_{roleId}";

            List<User> users;
            Expression<Func<User, bool>> filter = g => true;
            Expression<Func<User, bool>> filterByKeyword = g => g.UserName.Contains(keyword);
            Expression<Func<User, bool>> filterByStartDate = g => g.CreateDate >= startDate;
            Expression<Func<User, bool>> filterByEndDate = m => m.CreateDate <= endDate;
            Expression<Func<User, bool>> filterByRoleId = m => m.Roles.Any(r=>r.Id == roleId);

            if (!string.IsNullOrEmpty(keyword))
                filter = filter.AndAlso(filterByKeyword);
            if (startDate != null)
                filter = filter.AndAlso(filterByStartDate);
            if (endDate != null)
                filter = filter.AndAlso(filterByEndDate);
            if (roleId > 0)
                filter = filter.AndAlso(filterByRoleId);


            if (SettingsManager.User.EnableCaching)
            {
                if (_cacheService.IsSet(key))
                {
                    users = (List<User>)_cacheService.Get(key);
                }
                else
                {
                    users = _userRepository.GetPagedElements(pageIndex, pageSize, (c => c.CreateDate), filter, false, g => g.Roles).ToList();
                    _cacheService.Set(key, users, SettingsManager.Store.CacheDuration);
                }
            }
            else
            {
                users = _userRepository.GetPagedElements(pageIndex, pageSize, (c => c.CreateDate), filter, false, g => g.Roles).ToList();
            }

            return users;

        }



        public Task<User> GetUserByName(string userName, CancellationToken cancellationToken = default(CancellationToken))
        {
            return _userRepository.GetFirstOrDefaultAsync((u => u.UserName == userName), cancellationToken, (u => u.Roles));
        }
        public Task<User> GetUserByEmail(string email, CancellationToken cancellationToken = default(CancellationToken))
        {
            return  _userRepository.GetFirstOrDefaultAsync((u => u.Email == email), cancellationToken, (u => u.Roles)); 
        }

        public int Register(string userName,string email,string password,string realName)
        {
            var orgUsers = _userRepository.GetFilteredElements(u => u.Email == email);
            if (orgUsers.Count() > 0)
            {
                return 1; //1 邮箱已存在
            }

            orgUsers = _userRepository.GetFilteredElements(u => u.UserName == userName);
            if (orgUsers.Count() > 0)
            {
                return 2; //1 用户名已存在
            }
            
            
            var securityStamp = Hash.GenerateSalt();
            var passwordHash = Hash.HashPasswordWithSalt(password, securityStamp);

            var newUser = new User()
            {
                UserName = userName,
                RealName = realName,
                Email = email,
                SecurityStamp = Convert.ToBase64String(securityStamp),
                PasswordHash = passwordHash,
                CreateDate = DateTime.Now,
                IsActive = true
            };

            _logger.Info(string.Format(Logs.CreateMessage, newUser.EntityName, userName));
            Create(newUser);

           // SetUserCookies(false, newUser);

            return 0;

        }



        /// <summary>
        /// API注册
        /// </summary>
        /// <param name="vm"></param>
        /// <returns></returns>
        public async Task<int> RegisterForAPI(RegisterIMForAPI vm)
        {
            var orgUsers = await _userRepository.GetFilteredElementsAsync(u => u.Email == vm.Email);
            if (orgUsers.Count() > 0)
            {
                return 1; //1 邮箱已存在
            }

            orgUsers = await _userRepository.GetFilteredElementsAsync(u => u.UserName == vm.Username);
            if (orgUsers.Count() > 0)
            {
                return 2; //1 用户名已存在
            }


            var securityStamp = Hash.GenerateSalt();
            var passwordHash = Hash.HashPasswordWithSalt(vm.Password, securityStamp);

            var newUser = new User()
            {
                UserName = vm.Username,
                RealName = "",
                Email = vm.Email,
                SecurityStamp = Convert.ToBase64String(securityStamp),
                PasswordHash = passwordHash,
                CreateDate = DateTime.Now,
                IsActive = true
            };

            _logger.Info(string.Format(Logs.CreateMessage, newUser.EntityName, vm.Username));
            _userRepository.Add(newUser);
            await _userRepository.UnitOfWork.CommitAsync();
           // Create(newUser);

            SetUserCookies(false, newUser);

            return 0;
        }

        /// <summary>
        /// 用户登录
        /// </summary>
        /// <param name="model"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<User> SignIn(string username,string password, CancellationToken cancellationToken = default(CancellationToken))
        {
            User user = await _userRepository.GetFirstOrDefaultAsync((u=>u.UserName == username), cancellationToken, (u=>u.Roles));
          

            if (user != null)
            {
                // byte[] salt = Encoding.UTF8.GetBytes(user.SecurityStamp);
                byte[] salt = Convert.FromBase64String(user.SecurityStamp);
                string pwdHash = Hash.HashPasswordWithSalt(password, salt);

                if (user.PasswordHash == pwdHash)
                {
                    _logger.Info(string.Format(Logs.UserLoginMessage, user.UserName));
                    
                    return user;
                }
                else
                    return null;
            }

            return null;
        }

        public async Task<User> SignInForAPI(string username,string password, CancellationToken cancellationToken = default(CancellationToken))
        {
            User user = await _userRepository.GetFirstOrDefaultAsync((u => u.UserName == username), cancellationToken, (u => u.Roles));


            if (user != null)
            {
                // byte[] salt = Encoding.UTF8.GetBytes(user.SecurityStamp);
                byte[] salt = Convert.FromBase64String(user.SecurityStamp);
                string pwdHash = Hash.HashPasswordWithSalt(password, salt);

                if (user.PasswordHash == pwdHash)
                {
                    _logger.Info(string.Format(Logs.UserLoginMessage, user.UserName));

                    return user;
                }
                else
                    return null;
            }

            return null;
        }

        public List<Menu> GetUserMenus(Guid userId)
        {
            var key = $"{EntityNames.Menu}s_{Site.CurrentUserName}";

            List<Menu> menus;
            if (SettingsManager.Menu.EnableCaching)
            {
                if (_cacheService.IsSet(key))
                {
                    menus = (List<Menu>)_cacheService.Get(key);
                }
                else
                {
                    menus = _userRepository.GetUserMenus(userId);
                    _cacheService.Set(key, menus, SettingsManager.Menu.CacheDuration);
                }
            }
            else
            {
                menus = _userRepository.GetUserMenus(userId);
            }        

            return menus;
        }

        public void SetUserCookies(bool isPersist, User user)
        {
            var roles = user.Roles.Select(m => m.RoleName).ToArray();

            CustomPrincipalSerializeModel serializeModel = new CustomPrincipalSerializeModel();
            serializeModel.UserId = user.Id;
            serializeModel.RealName = user.RealName;
            serializeModel.Avatar = string.IsNullOrEmpty(user.PhotoUrl) ? SettingsManager.User.DefaultAvatar : user.PhotoUrl;
            serializeModel.Roles = roles;
            //serializeModel.Menus = GetUserMenus(user.);
            TimeSpan timeout = FormsAuthentication.Timeout;
            DateTime expire = DateTime.Now.Add(timeout);


            string userData = JsonConvert.SerializeObject(serializeModel, Formatting.Indented,
                new JsonSerializerSettings()
                {
                    ReferenceLoopHandling = ReferenceLoopHandling.Ignore
                });


            FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
                     1,
                     user.UserName,
                     DateTime.Now,
                     expire,
                     isPersist,
                     userData);

            string encTicket = FormsAuthentication.Encrypt(authTicket);
            HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
            HttpContext.Current.Response.Cookies.Add(faCookie);
        }

        /// <summary>
        /// 重设密码
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="password"></param>
        /// <returns></returns>

        public bool SetPassword(Guid userId, string password)

        {
            var user = GetById(userId);
            try
            {              

                var securityStamp = Hash.GenerateSalt();
                var pwdHash = Hash.HashPasswordWithSalt(password, securityStamp);

                user.SecurityStamp = Convert.ToBase64String(securityStamp);
                user.PasswordHash = pwdHash;
                base.Update(user);
                //log 

                _logger.Info(string.Format(Logs.RestPwdMessage, user.EntityName, user.UserName));



                return true;
            }
            catch (Exception er)
            {
                var message = String.Format(Logs.ErrorRestPwdMessage, user.EntityName);
                _logger.Error(message, er);
                return false;
            }
        }


        public bool IsEmailUniqueAtEdit(string email, Guid id)
        {
            var result = _userRepository.GetAllElements().Any(u => u.Email == email && u.Id != id);
            if (result)
                return false;

            return true;
        }

        

    }
}